﻿using System;
using System.Collections.Generic;
using ICSharpCode.SharpZipLib.Zip;
using System.IO;
using ICSharpCode.SharpZipLib.Checksums;
namespace WindowServices.Common
{
    public class ZipHelper
    {
        public static bool UnZip(string filename, string targetfolder, string password)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException("filename");
            }

            if (string.IsNullOrEmpty(targetfolder))
            {
                throw new ArgumentNullException("targetfolder");
            }

            if (!File.Exists(filename))
            {
                throw new Exception("file not exit:" + filename);
            }

            using (FileStream stream = File.OpenRead(filename))
            {
                return UnZip(stream, targetfolder, password);
            }
        }

        public static bool UnZip(Stream stream, string targetfoler, string password)
        {
            return UnZip(stream, new DirectoryInfo(targetfoler), password);
        }

        public static bool UnZip(Stream stream, DirectoryInfo targetfoler, string password)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (!targetfoler.Exists)
            {
                targetfoler.Create();
            }

            ZipInputStream zip = null;
            try
            {
                zip = new ZipInputStream(stream);
                if (!string.IsNullOrEmpty(password))
                {
                    zip.Password = password;
                }

                ZipEntry entity = zip.GetNextEntry();
                while (entity != null)
                {
                    WriteEntity(zip, entity, targetfoler);
                    entity = zip.GetNextEntry();
                }
            }
            finally
            {
                if (zip != null)
                {
                    zip.Close();
                }
            }

            return true;
        }

        private static Stream UnZip(ZipInputStream zip)
        {
            var buffer = new byte[BlockSize];
            var file = new MemoryStream();
            int count;
            do
            {
                count = zip.Read(buffer, 0, BlockSize);
                if (count > 0)
                {
                    file.Write(buffer, 0, count);
                }
            }
            while (count != BlockSize);
            return file;
        }

        private const int BlockSize = 2048;

        private static void WriteEntity(ZipInputStream zip, ZipEntry entity, DirectoryInfo targetfolder)
        {
            var buffer = new byte[BlockSize];
            var targetFile = string.Format("{0}\\{1}", targetfolder, entity.Name);
            if (entity.Name.Contains("\\"))
            {
                var dir = targetFile.Substring(0, targetFile.LastIndexOf('\\'));

                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
            }
            using (var file = new FileStream(targetFile, FileMode.OpenOrCreate, FileAccess.Write))
            {
                int count;
                do
                {
                    count = zip.Read(buffer, 0, BlockSize);
                    if (count > 0)
                    {
                        file.Write(buffer, 0, count);
                    }
                }
                while (count == BlockSize);
            }

        }

        public static bool Zip(string sourcefolder, string targetfile, string password, bool subfolder, Dictionary<string, Stream> dicmemoryfile)
        {
            if (string.IsNullOrEmpty(sourcefolder))
            {
                throw new ArgumentNullException("sourcefolder");
            }

            if (string.IsNullOrEmpty(targetfile))
            {
                throw new ArgumentNullException("targetfile");
            }

            if (!Directory.Exists(sourcefolder))
            {
                throw new Exception("folder is not exits:" + sourcefolder);
            }

            string tmp = Path.GetDirectoryName(targetfile);
            if (!Directory.Exists(tmp))
            {
                Directory.CreateDirectory(tmp);
            }

            ZipOutputStream zip = null;
            Crc32 crc = new Crc32();
            try
            {
                using (FileStream file = new FileStream(targetfile, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    zip = new ZipOutputStream(file);
                    if (!string.IsNullOrEmpty(password))
                    {
                        zip.Password = password;
                    }
                    ZipFolder(ref zip, sourcefolder, subfolder, crc, sourcefolder);

                    foreach (string filename in dicmemoryfile.Keys)
                    {
                        WriteStream(ref zip, filename.Replace(sourcefolder, ""), dicmemoryfile[filename], crc);
                    }
                }
            }
            finally
            {
                if (zip != null)
                {
                    zip.Close();
                }
            }

            return true;
        }

        private static void ZipFolder(ref ZipOutputStream zip, string folder, bool subfolder, Crc32 crc, string cutString)
        {
            var di = new DirectoryInfo(folder);
            foreach (var fi in di.GetFiles("*.*"))
            {
                using (var fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read))
                {
                    WriteStream(ref zip, fi.FullName.Replace(cutString, ""), fs, crc);
                }

            }

            if (subfolder)
            {
                foreach (var di1 in di.GetDirectories())
                {
                    ZipFolder(ref zip, di1.FullName, true, crc, cutString);
                }
            }


        }

        private static void WriteStream(ref ZipOutputStream zip, string entityname, Stream stream, Crc32 crc)
        {
            var buffer = new byte[BlockSize];
            int count;
            var entity = new ZipEntry(entityname) { DateTime = DateTime.Now };
            zip.PutNextEntry(entity);
            crc.Reset();
            do
            {
                count = stream.Read(buffer, 0, BlockSize);
                if (count > 0)
                {
                    crc.Update(buffer, 0, count);
                    zip.Write(buffer, 0, count);
                }
            }
            while (count == BlockSize);
            entity.Crc = crc.Value;
        }
    }
}
